package com.dragon.sso.config;

import com.dragon.sso.service.CustomAuthenticationProvider;
import com.dragon.sso.service.LdapAuthenticationProvider;
import com.dragon.sso.service.LdapUserDetailService;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.web.filter.CorsFilter;

/**
 * Create date 2020/6/15. 安全配置： 用户信息，密码编码器，安全拦截机制
 *
 * @author evan
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AuthWebSecurityConfiguration extends WebSecurityConfigurerAdapter
    implements InitializingBean {

  private final AuthenticationManagerBuilder authenticationManagerBuilder;
  private final LdapAuthenticationProvider ldapAuthenticationProvider;
  private final CustomAuthenticationProvider customAuthenticationProvider;

  public AuthWebSecurityConfiguration(
      AuthenticationManagerBuilder authenticationManagerBuilder,
      CustomAuthenticationProvider customAuthenticationProvider,
      LdapAuthenticationProvider ldapAuthenticationProvider) {
    this.authenticationManagerBuilder = authenticationManagerBuilder;
    this.ldapAuthenticationProvider = ldapAuthenticationProvider;
    this.customAuthenticationProvider = customAuthenticationProvider;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    try {
      // 用于身份验证
      authenticationManagerBuilder
          .authenticationProvider(customAuthenticationProvider)
          .authenticationProvider(ldapAuthenticationProvider)
          .eraseCredentials(false);
    } catch (Exception e) {
      throw new BeanInitializationException("Security configuration failed", e);
    }
  }

  /**
   * 用于身份验证
   *
   * @return
   * @throws Exception
   */
  @Override
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  /**
   * 资源访问规则
   *
   * @param web
   * @throws Exception
   */
  @Override
  public void configure(WebSecurity web) throws Exception {
    HttpSecurity http = getHttp();
    web.ignoring()
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .antMatchers("/test/**")
        .antMatchers("**/*.png")
        .antMatchers("/h2-console/**")
        .antMatchers("/error");
  }

  @Bean
  public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
    return new SecurityEvaluationContextExtension();
  }

  @EnableResourceServer
  @Configuration
  public static class AuthResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private final TokenStore tokenStore;
    private final CorsFilter corsFilter;
    private final PersistentTokenRepository persistentTokenRepository;
    private UserDetailsService userDetailsService;

    public AuthResourceServerConfiguration(
        TokenStore tokenStore,
        CorsFilter corsFilter,
        LdapUserDetailService userDetailService,
        PersistentTokenRepository persistentTokenRepository) {
      this.tokenStore = tokenStore;
      this.corsFilter = corsFilter;
      this.persistentTokenRepository = persistentTokenRepository;
      this.userDetailsService = userDetailService;
    }

    /**
     * 安全拦截机制
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
      // 设置共享变量，目的是想要在登陆成功之后，正确重定向
      http.setSharedObject(RequestCache.class, new HttpSessionRequestCache());
      http.addFilter(corsFilter);
      http.exceptionHandling()
          .and()
          .csrf()
          .disable()
          .headers()
          .frameOptions()
          .disable()
          .and()
          .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
          .and()
          .formLogin().loginPage("/login")
          .defaultSuccessUrl("/index")
          .failureUrl("/login?error=true")
          .permitAll()
          .and()
          .logout()
          .deleteCookies("JSESSIONIDYY").logoutSuccessHandler(
          new SimpleUrlLogoutSuccessHandler() {{
            this.setTargetUrlParameter("target");
          }})
          .and()
          .rememberMe()
          .tokenRepository(persistentTokenRepository)
          .userDetailsService(userDetailsService)
          .and()
          .authorizeRequests()
          .antMatchers("/userinfo")
          .permitAll();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      resources.resourceId("uaa").tokenStore(tokenStore);
    }
  }
}
